package cn.lingyangwl.agile.tenant.core;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.lingyangwl.agile.common.core.exception.enums.*;
import cn.lingyangwl.agile.model.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.agile.tenant.core.mapper.*;
import cn.lingyangwl.agile.tenant.core.utils.*;
import cn.lingyangwl.framework.core.utils.spring.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 处理租户非共享数据(其中有一部分共享数据)
 *
 * @author shenguangyang
 */
@Slf4j
public class TenantDataIsolateServiceImpl extends ServiceImpl<TenantDataMapper, TenantData>
    implements TenantDataIsolateService {
    private static final Map<ITenantDataType, EntityCache> cacheMap = new ConcurrentHashMap<>();
    private static final Map<Class<?>, EntityCache> cacheByClassMap = new ConcurrentHashMap<>();

    @Data
    public static class EntityCache {
        /**
         * 租户隔离的字段
         */
        private List<Field> isolateFields = Collections.emptyList();

        private Field idField;

        private ITenantDataType type;
    }

    @Override
    public void addDataType(ITenantDataType tenantDataType) {
        EntityCache cache = new EntityCache();
        Field[] fields = ReflectUtil.getFields(tenantDataType.getEntityClass());

        List<Field> isolateFields = new ArrayList<>();
        Field idField = null;
        for (Field field : fields) {
            if (field.isAnnotationPresent(DataPartIsolation.class)) {
                field.setAccessible(true);
                isolateFields.add(field);
            } else if (field.isAnnotationPresent(TableId.class)){
                field.setAccessible(true);
                idField = field;
            } else if ("id".equals(field.getName())){
                field.setAccessible(true);
                idField = field;
            }
        }
        if (Objects.isNull(idField)) {
            throw new RuntimeException("未查找到 " + tenantDataType.getEntityClass().getName() + " 的主键字段");
        }

        cache.setIsolateFields(isolateFields);
        cache.setIdField(idField);
        cache.setType(tenantDataType);
        cacheMap.put(tenantDataType, cache);
        cacheByClassMap.put(tenantDataType.getEntityClass(), cache);
    }

    @Override
    public void processQueryResult(List<? extends BasePO> list) {
        String rawTenantId = TenantContext.get().getRawTenantId();
        String mainTenantId = TenantContext.get().getMainTenantId();
        String mgtSceneTenantId = TenantContext.get().getMgtSceneTenantId();
        if (CollUtil.isEmpty(list)) {
            return;
        }

        Object entity = list.get(0);
        if (Objects.isNull(entity)) {
            return;
        }
        EntityCache entityCache = cacheByClassMap.get(entity.getClass());
        if (Objects.isNull(entityCache)) {
            return;
        }

        // key = id, value: 实体
        Map<String, Object> entityMap = new HashMap<>();
        List<String> dataIds = list.stream().map(e -> {
            try {
                String id = String.valueOf(entityCache.getIdField().get(e));
                entityMap.put(id, e);
                return id;
            } catch (Exception ex) {
                log.error("error: ", ex);
                throw new BizException("查询失败, 请联系服务商");
            }
        }).collect(Collectors.toList());

        if (CollUtil.isEmpty(dataIds)) {
            log.warn("数据ids为空");
            return;
        }

        // 查询租户数据表(用于数据部分隔离)
        Set<String> tenantIds = new HashSet<>();
        tenantIds.add(mainTenantId);
        tenantIds.add(rawTenantId);
        tenantIds.add(mgtSceneTenantId);
        LambdaQueryWrapper<TenantData> lqw = TenantData.lqw().eq(TenantData::getBizType, entityCache.getType().getType())
            .in(TenantData::getTenantId, tenantIds).in(TenantData::getBizId, dataIds);
        Map<String, JSONObject> isolateDataMap = this.list(lqw).stream()
            .collect(Collectors.toMap(e -> e.getBizId() + ":" + e.getTenantId(), (e) -> JSON.parseObject(e.getContent())));

        JSONObject empty = new JSONObject();
        entityMap.forEach((id, obj) -> {
            entityCache.getIsolateFields().forEach(field -> {
                DataPartIsolation annot = field.getAnnotation(DataPartIsolation.class);
                Object value;
                String key;
                if (annot.level() == DataIsolateLevel.ALONE) {
                    key = id + ":" + rawTenantId;
                } else if (annot.level() == DataIsolateLevel.MGT) {
                    key = id + ":" + mgtSceneTenantId;
                } else if (annot.level() == DataIsolateLevel.MAIN) {
                    key = id + ":" + mainTenantId;
                } else {
                    log.warn("尚不支持, 数据部分隔离级别 [{}], class: {}, fieldName: {}",
                        annot.level().name(), obj.getClass().getSimpleName(), field.getName());
                    return;
                }
                value = isolateDataMap.getOrDefault(key, empty).get(field.getName());
                if (Objects.isNull(value)) {
                    return;
                }

                try {
                    field.set(obj, value);
                } catch (Exception ex) {
                    log.error("error: ", ex);
                    throw new BizException("查询失败");
                }
            });
        });

    }

    @Override
    public <T extends BasePO> void processQueryResult(T entity) {
        this.processQueryResult(Collections.singletonList(entity));
    }

    @Override
    public void deleteData(ITenantDataType bizType, List<Long> ids) {
        EntityCache entityCache = cacheMap.get(bizType);
        if (Objects.isNull(entityCache) || CollUtil.isEmpty(ids)) {
            return;
        }
        TenantUtils.executeIgnore(DataIsolateLevel.ALL, () -> {
            LambdaQueryWrapper<TenantData> lqw = TenantData.lqw().in(TenantData::getBizId, ids)
                .eq(TenantData::getBizType, entityCache.getType().getType());
            this.remove(lqw);
        });
    }

    @Override
    public void deleteData(ITenantDataType bizType, Long id) {
        TenantDataIsolateService thisProxy = SpringUtils.getAopProxy(this);
        thisProxy.deleteData(bizType, Collections.singletonList(id));
    }

    @Override
    public <T extends BasePO> void saveData(T entity) {
        TenantContext tenantContext = TenantContext.get();
        if (tenantContext.isMgtSceneTenant()) {
            log.warn("当前租户 [{}] 非管理场景的租户, 不允许进行新增操作", tenantContext.getTenantId());
            return;
        }
        try {
            if (Objects.isNull(entity)) {
                return;
            }
            Class<? extends BasePO> targetClass = entity.getClass();
            EntityCache entityCache = cacheByClassMap.get(targetClass);
            if (Objects.isNull(entityCache)) {
                return;
            }

            String id = (String) entityCache.getIdField().get(entity);
            String rawTenantId = TenantContext.get().getRawTenantId();

            JSONObject isolateData = getIsolateData(entityCache, entity);
            checkWrite(entityCache, isolateData);
            TenantData tenantData = TenantData.builder().tenantId(rawTenantId)
                .bizId(String.valueOf(id)).bizType(entityCache.getType().getType())
                .content(isolateData.toJSONString()).build();

            this.save(tenantData);
        } catch (Exception e) {
            log.error("error: ", e);
            throw new BizException(GlobalErrorEnum.OPERATION_FAIL_CREATE);
        }
    }

    public JSONObject getIsolateData(EntityCache entityCache, Object entity) {
        JSONObject resp = new JSONObject();
        try {
            List<Field> isolateFields = entityCache.getIsolateFields();
            for (Field isolateField : isolateFields) {
                resp.put(isolateField.getName(), isolateField.get(entity));
            }
        } catch (Exception e) {
            log.error("error: ", e);
            throw new BizException("获取数据失败, 请联系管理员");
        }
        return resp;
    }

    @Override
    public <T extends BasePO> void updateData(T entity) {
       try {
           if (Objects.isNull(entity)) {
               return;
           }
           Class<? extends BasePO> targetClass = entity.getClass();
           EntityCache entityCache = cacheByClassMap.get(targetClass);
           if (Objects.isNull(targetClass)) {
               return;
           }

           Object id = entityCache.getIdField().get(entity);
           if (Objects.isNull(id)) {
               log.warn("主键为空, 无法处理租户数据部分字段隔离");
               return;
           }

           JSONObject isolateData = getIsolateData(entityCache, entity);

           String rawTenantId = TenantContext.get().getRawTenantId();
           checkWrite(entityCache, isolateData);
           LambdaUpdateWrapper<TenantData> luw = TenantData.luw()
               .eq(TenantData::getTenantId, rawTenantId).eq(TenantData::getBizId, id)
               .eq(TenantData::getBizType, entityCache.getType().getType())
               .set(TenantData::getContent, isolateData.toJSONString());

           TenantData dbTenantData = this.getOne(luw);
           if (Objects.isNull(dbTenantData)) {
               TenantData tenantData = TenantData.builder().tenantId(rawTenantId)
                   .bizId(String.valueOf(id)).bizType(entityCache.getType().getType())
                   .content(isolateData.toJSONString()).build();
               this.save(tenantData);
               return;
           }

           this.update(luw);
       } catch (Exception e) {
           log.error("error: ", e);
           throw new BizException("更新或保存异常");
       }
    }

    /**
     * 检查写入操作, 比如检查如果字段是主租户隔离级别, 那么在子租户中设置时需要将其置空, 不更新到数据库中
     * 避免在查询时候没有采用主租户的配置数据导致一些问题
     */
    public void checkWrite(EntityCache entityCache, JSONObject isolateData ) {
        entityCache.getIsolateFields().forEach(e -> {
            String fieldName = e.getName();

            DataPartIsolation annot = e.getAnnotation(DataPartIsolation.class);
            boolean mainTenant = TenantContext.get().isMainTenant();
            boolean isMgt = TenantContext.get().getIsMgtScene();
            if (annot.level() == DataIsolateLevel.MAIN && !mainTenant && isolateData.containsKey(fieldName)) {
                isolateData.remove(fieldName);
            } else if (annot.level() == DataIsolateLevel.MGT && !isMgt && isolateData.containsKey(fieldName)) {
                isolateData.remove(fieldName);
            }
        });
    }
}
